#include "vm_type_register.hpp"
#include <imgui/imgui.h>
#include <stdint.h>
#include <cassert>
#include <cstdint>
#include <string>
#include <type_traits>
#include <vector>
#include "angelscript.h"
#include "angelscript/add_on/autowrapper/aswrappedcall.h"
#include "data/editor_data.hpp"
#include "fmt/format.h"
#include "scripting/extra/info_api.hpp"
#include "sql/types.hpp"
#include "utils/app_utils.hpp"
#include "utils/date_utils.hpp"
#include "utils/log.hpp"
#include "utils/path_utils.hpp"
#include "utils/shell_utils.hpp"
#include "sql/sql_data_extensions.hpp"

static void print (std::string msg)
{
    printf ("%s", msg.c_str ());
}

static void println (std::string msg)
{
    printf ("%s\n", msg.c_str ());
}

namespace SystemFuncs
{
    void CopyText (std::string text)
    {
        ::CopyText(text);
    }
    void I (std::string msg)
    {
        SendInfo (msg);
    }
    void W (std::string msg)
    {
        SendWarning (msg);
    }
    void E (std::string msg)
    {
        SendError (msg);
    }
    void P (bool show)
    {
        ShowProgressDialog (show);
    }
    void N (std::string msg, double ms)
    {
        SendNotification (msg, ms);
    }
    void LogD (std::string msg)
    {
        log_printf("<script>", 1, LOG_LEVEL_DEBUG, "%s", msg.c_str());
    }
    void LogI (std::string msg)
    {
        log_printf("<script>", 1, LOG_LEVEL_INFO, "%s", msg.c_str());
    }
    void LogW (std::string msg)
    {
        log_printf("<script>", 1, LOG_LEVEL_WARN, "%s", msg.c_str());
    }
    void LogE (std::string msg)
    {
        log_printf("<script>", 1, LOG_LEVEL_ERROR, "%s", msg.c_str());
    }
    std::string TempPath(const std::string &rel)
    {
        return GetTempFilePath(rel.c_str());
    }
    std::string AssetPath(const std::string &rel)
    {
        return GetAssetsPath(rel.c_str());
    }
    std::string DataPath(const std::string &rel)
    {
        return GetDataPath(rel.c_str());
    }
    std::string AppVersionStr()
    {
        return GetAppVersionText();
    }
    std::string AppVersionInfo()
    {
        return GetAppVersionInfo();
    }
    std::string DataVersionStr()
    {
        return GetDataVersion();
    }
}  // namespace SystemFuncs

#define AS_VERIFY(x)        \
    do                      \
    {                       \
        int const code = x; \
        assert (code >= 0); \
    } while (0)

static void IncRef (void *)
{ /*just a empty func*/
}
static void DecRef (void *)
{ /*just a empty func*/
}

#define DATA_TYPE(N, T)                                                                                                            \
    do                                                                                                                             \
    {                                                                                                                              \
        AS_VERIFY (engine->RegisterObjectType (N, sizeof (T), asOBJ_REF));                                                         \
        AS_VERIFY (engine->RegisterObjectBehaviour (N, asBEHAVE_ADDREF, "void f()", asFUNCTION (IncRef), asCALL_CDECL_OBJFIRST));  \
        AS_VERIFY (engine->RegisterObjectBehaviour (N, asBEHAVE_RELEASE, "void f()", asFUNCTION (DecRef), asCALL_CDECL_OBJFIRST)); \
    } while (0)

#define FMT(buffer, format, ...)                        \
    do                                                  \
    {                                                   \
        memset (buffer, 0, sizeof (buffer));            \
        fmt::format_to (buffer, format, ##__VA_ARGS__); \
    } while (0)

template <typename T>
static void RegisterVectorType (asIScriptEngine *engine, const char *container_name, const char *element_name)
{
    using con_t  = std::vector<T>;
    using iter_t = typename con_t::iterator;

    struct VectorIter {
        con_t *con;
        iter_t iter;
        int    ref;

        void AddRef() {
            ref++;
        }

        void Release() {
            if (--ref == 0) {
                delete this;
            }
        }

        bool end () const
        {
            return iter == con->end ();
        }

        T *value () const
        {
            return &(*iter);
        }

        bool next ()
        {
            ++iter;
            return !end();
        }

        static void* FromContainer (con_t *con)
        {
            auto* r = new VectorIter {.con = con, .iter = con->begin (), .ref = 0};
            r->AddRef();
            return r;
        }

        static uint32_t ContainerSize(con_t &con) {
            return (uint32_t)con.size();
        }

        static bool ContainerIsEmpty(con_t &con) {
            return ContainerSize(con) == 0;
        }
    };

    char itername[128];
    char func_name[128];
    FMT (itername, "{}_Iterator", container_name);

    DATA_TYPE (container_name, con_t);
    AS_VERIFY (engine->RegisterObjectType (itername, sizeof (VectorIter), asOBJ_REF));
    FMT (func_name, "void f({}@)", container_name);
    AS_VERIFY (engine->RegisterObjectBehaviour (itername, asBEHAVE_ADDREF, "void f()", asMETHOD (VectorIter, AddRef), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectBehaviour (itername, asBEHAVE_RELEASE, "void f()", asMETHOD (VectorIter, Release), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectMethod (itername, "bool end() const", asMETHOD (VectorIter, end), asCALL_THISCALL));
    FMT (func_name, "{}@ value() const", element_name);
    AS_VERIFY (engine->RegisterObjectMethod (itername, func_name, asMETHOD (VectorIter, value), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectMethod (itername, "bool next()", asMETHOD (VectorIter, next), asCALL_THISCALL));
    FMT (func_name, "{}@ iter() const", itername);
    AS_VERIFY (engine->RegisterObjectMethod (container_name, func_name, asFUNCTION (&VectorIter::FromContainer), asCALL_CDECL_OBJFIRST));
    AS_VERIFY (engine->RegisterObjectMethod (container_name, "uint size() const", asFUNCTION (&VectorIter::ContainerSize), asCALL_CDECL_OBJFIRST));
    AS_VERIFY (engine->RegisterObjectMethod (container_name, "bool empty() const", asFUNCTION (&VectorIter::ContainerIsEmpty), asCALL_CDECL_OBJFIRST));
}

template <typename T>
static void RegisterListType (asIScriptEngine *engine, const char *container_name, const char *element_name)
{
    using con_t  = std::list<T>;
    using iter_t = typename con_t::iterator;

    struct ListIter {
        con_t *con;
        iter_t iter;
        int    ref;

        void AddRef() {
            ref++;
        }

        void Release() {
            if (--ref == 0) {
                delete this;
            }
        }

        bool end () const
        {
            return iter == con->end ();
        }

        T *value () const
        {
            return &(*iter);
        }

        bool next ()
        {
            ++iter;
            return !end();
        }

        static void* FromContainer (con_t *con)
        {
            auto* r = new ListIter {.con = con, .iter = con->begin (), .ref = 0};
            r->AddRef();
            return r;
        }

        static uint32_t ContainerSize(con_t &con) {
            return (uint32_t)con.size();
        }

        static bool ContainerIsEmpty(con_t &con) {
            return ContainerSize(con) == 0;
        }
    };

    char itername[128];
    char func_name[128];
    FMT (itername, "{}_Iterator", container_name);

    DATA_TYPE (container_name, con_t);
    AS_VERIFY (engine->RegisterObjectType (itername, sizeof (ListIter), asOBJ_REF));
    FMT (func_name, "void f({}@)", container_name);
    AS_VERIFY (engine->RegisterObjectBehaviour (itername, asBEHAVE_ADDREF, "void f()", asMETHOD (ListIter, AddRef), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectBehaviour (itername, asBEHAVE_RELEASE, "void f()", asMETHOD (ListIter, Release), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectMethod (itername, "bool end() const", asMETHOD (ListIter, end), asCALL_THISCALL));
    FMT (func_name, "{}@ value() const", element_name);
    AS_VERIFY (engine->RegisterObjectMethod (itername, func_name, asMETHOD (ListIter, value), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectMethod (itername, "bool next()", asMETHOD (ListIter, next), asCALL_THISCALL));
    FMT (func_name, "{}@ iter() const", itername);
    AS_VERIFY (engine->RegisterObjectMethod (container_name, func_name, asFUNCTION (&ListIter::FromContainer), asCALL_CDECL_OBJFIRST));
    AS_VERIFY (engine->RegisterObjectMethod (container_name, "uint size() const", asFUNCTION (&ListIter::ContainerSize), asCALL_CDECL_OBJFIRST));
    AS_VERIFY (engine->RegisterObjectMethod (container_name, "bool empty() const", asFUNCTION (&ListIter::ContainerIsEmpty), asCALL_CDECL_OBJFIRST));
}

template <typename S, typename E>
static std::list<E> *ChildrenWrapper(S *s) {
    return &s->children;
}

static std::vector<Tag> *ItemNodeTags(ItemNode *node) {
    return &node->item_tags;
}

static void RegisterEditorTypes (asIScriptEngine *engine)
{
    DATA_TYPE ("Tag", Tag);
    AS_VERIFY (engine->RegisterObjectProperty ("Tag", "string name", asOFFSET (Tag, tag_name)));
    AS_VERIFY (engine->RegisterObjectProperty ("Tag", "uint level", asOFFSET (Tag, tag_level)));
    AS_VERIFY (engine->RegisterObjectProperty ("Tag", "uint color", asOFFSET (Tag, tag_color)));
    RegisterVectorType<Tag> (engine, "Tag_Vec", "Tag");

    DATA_TYPE ("WorkItem", WorkItem);
    AS_VERIFY (engine->RegisterObjectProperty ("WorkItem", "string create_date", asOFFSET (WorkItem, create_date)));
    AS_VERIFY (engine->RegisterObjectProperty ("WorkItem", "double hour", asOFFSET (WorkItem, hour)));
    AS_VERIFY (engine->RegisterObjectProperty ("WorkItem", "string comment", asOFFSET (WorkItem, comment)));
    AS_VERIFY (engine->RegisterObjectProperty ("WorkItem", "string note", asOFFSET (WorkItem, note)));
    AS_VERIFY (engine->RegisterObjectProperty ("WorkItem", "int act", asOFFSET (WorkItem, act_id)));
    AS_VERIFY (engine->RegisterObjectProperty ("WorkItem", "int issue", asOFFSET (WorkItem, issue_id)));

    DATA_TYPE ("ItemNode", ItemNode);
    AS_VERIFY (engine->RegisterObjectProperty ("ItemNode", "WorkItem item", asOFFSET (ItemNode, item)));
    AS_VERIFY (engine->RegisterObjectMethod ("ItemNode", "Tag_Vec &tags() const", asFUNCTION(&ItemNodeTags), asCALL_CDECL_OBJFIRST));
    RegisterListType<ItemNode> (engine, "ItemNode_List", "ItemNode");

    DATA_TYPE ("DailyItem", DayLevelNode);
    AS_VERIFY (engine->RegisterObjectProperty ("DailyItem", "string title", asOFFSET (DayLevelNode, title)));
    AS_VERIFY (engine->RegisterObjectProperty ("DailyItem", "string key", asOFFSET (DayLevelNode, child_key)));
    AS_VERIFY (engine->RegisterObjectMethod ("DailyItem", "ItemNode_List &children() const", asFUNCTION((&ChildrenWrapper<DayLevelNode, ItemNode>)), asCALL_CDECL_OBJFIRST));
    AS_VERIFY (engine->RegisterObjectMethod ("DailyItem", "void load()", asMETHOD(DayLevelNode, LoadInnerData), asCALL_THISCALL));
    RegisterListType<DayLevelNode> (engine, "DailyItem_List", "DailyItem");

    DATA_TYPE ("MonthlyItem", MonthLevelNode);
    AS_VERIFY (engine->RegisterObjectProperty ("MonthlyItem", "string title", asOFFSET (MonthLevelNode, title)));
    AS_VERIFY (engine->RegisterObjectProperty ("MonthlyItem", "string key", asOFFSET (MonthLevelNode, child_key)));
    AS_VERIFY (engine->RegisterObjectMethod ("MonthlyItem", "DailyItem_List &children() const", asFUNCTION((&ChildrenWrapper<MonthLevelNode, DayLevelNode>)), asCALL_CDECL_OBJFIRST));
    AS_VERIFY(engine->RegisterObjectMethod("MonthlyItem", "void load()", asMETHOD(MonthLevelNode, LoadInnerData), asCALL_THISCALL));
    RegisterListType<MonthLevelNode> (engine, "MonthlyItem_List", "MonthlyItem");

    DATA_TYPE ("YearlyItem", YearLevelNode);
    AS_VERIFY (engine->RegisterObjectProperty ("YearlyItem", "string title", asOFFSET (YearLevelNode, title)));
    AS_VERIFY (engine->RegisterObjectProperty ("YearlyItem", "string key", asOFFSET (YearLevelNode, child_key)));
    AS_VERIFY(engine->RegisterObjectMethod("YearlyItem", "void load()", asMETHOD(YearLevelNode, LoadInnerData), asCALL_THISCALL));
    AS_VERIFY (engine->RegisterObjectMethod ("YearlyItem", "MonthlyItem_List &children() const", asFUNCTION((&ChildrenWrapper<YearLevelNode, MonthLevelNode>)), asCALL_CDECL_OBJFIRST));
}

void RegisterTypes (asIScriptEngine *engine)
{
    int r;

    // string vector
    // RegisterVectorType<std::string>(engine, "string_vec", "string");

    // print
    r = engine->RegisterGlobalFunction ("void print(string)", asFUNCTION (print), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void println(string)", asFUNCTION (println), asCALL_CDECL); assert (r >= 0);

    // status message
    r = engine->RegisterGlobalFunction ("void Notification(string,double)", asFUNCTION (SystemFuncs::N), asCALL_CDECL); assert (r >= 0);
    // clipboard function
    r = engine->RegisterGlobalFunction ("void CopyText(string)", asFUNCTION (SystemFuncs::CopyText), asCALL_CDECL); assert (r >= 0);
    // version
    r = engine->RegisterGlobalFunction ("string AppVersionStr()", asFUNCTION (SystemFuncs::AppVersionStr), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string AppVersionInfo()", asFUNCTION (SystemFuncs::AppVersionInfo), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("uint64 AppVersionCode()", asFUNCTION (GetAppVersionCode), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string DataVersionStr()", asFUNCTION (SystemFuncs::DataVersionStr), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("uint64 DataVersionCode()", asFUNCTION (GetDataVersionCode), asCALL_CDECL); assert (r >= 0);
    // info dialog
    r = engine->RegisterGlobalFunction ("void ShowInfo(string)", asFUNCTION (SystemFuncs::I), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void ShowWarning(string)", asFUNCTION (SystemFuncs::W), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void Error(string)", asFUNCTION (SystemFuncs::E), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void Progress(bool)", asFUNCTION (SystemFuncs::P), asCALL_CDECL); assert (r >= 0);
    // logging
    r = engine->RegisterGlobalFunction ("void LogD(string)", asFUNCTION (SystemFuncs::LogD), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void LogI(string)", asFUNCTION (SystemFuncs::LogI), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void LogW(string)", asFUNCTION (SystemFuncs::LogW), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void LogE(string)", asFUNCTION (SystemFuncs::LogE), asCALL_CDECL); assert (r >= 0);
    // shell utils
    r = engine->RegisterGlobalFunction ("string ChooseFile(string=\"\")", asFUNCTION (ChooseFile), asCALL_CDECL); assert (r >= 0);
    // r = engine->RegisterGlobalFunction ("string_vec ChooseFiles(string=\"\")", asFUNCTION (ChooseFiles), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string SaveFile(string,string=\"\")", asFUNCTION (SaveFile), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string PickFolder()", asFUNCTION (PickFolder), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string ShowLogs()", asFUNCTION (ShowLogFileContent), asCALL_CDECL); assert (r >= 0);
    // date utils
    r = engine->RegisterGlobalFunction ("string Yestoday()", asFUNCTION (GetYesterday), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string Today()", asFUNCTION (GetToday), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string Tomorrow()", asFUNCTION (GetTomorrow), asCALL_CDECL); assert (r >= 0);
    // path utils
    r = engine->RegisterGlobalFunction ("string TempPath(string)", asFUNCTION (SystemFuncs::TempPath), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string AssetPath(string)", asFUNCTION (SystemFuncs::AssetPath), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("string DataPath(string)", asFUNCTION (SystemFuncs::DataPath), asCALL_CDECL); assert (r >= 0);
    // app utils
    r = engine->RegisterGlobalFunction ("void CreateWork(string,string,double)", asFUNCTION (CreateWorkItem), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void CreateWorkWithNote(string,string,double,string)", asFUNCTION (CreateWorkItemWithNote), asCALL_CDECL); assert (r >= 0);
    r = engine->RegisterGlobalFunction ("void ShowStatistics(string,string)", asFUNCTION (ShowStatistics), asCALL_CDECL); assert (r >= 0);
    // data ext
    r = engine->RegisterGlobalFunction("string GetIssueProjectDesc(int)", asFUNCTION(sql_data_ext.get_issue_project_desc), asCALL_CDECL); assert(r >= 0);

    // editor types
    RegisterEditorTypes (engine);
}
